package communication;

import java.util.ArrayList;
import java.util.Hashtable;

public class BarcodeAnalyserRobot {
	
	public Queue readings = new Queue(500);
	private ArrayList<String> barcodes = new ArrayList<String>();
	private ArrayList<Integer> lastPossible = new ArrayList<Integer>();
	private ArrayList<Integer> temp = new ArrayList<Integer>();
	private ArrayList<Integer> colors = new ArrayList<Integer>();
	private int nbBrownValuesMargin=15; //the margin of brown values before and after a barcode

	
	private int brownValue = 509; // was 496
	private int whiteMargin=20; //offset from brown value
	private int blackMargin=30; //offset from brown value
	private int BCLength = 8; //number of unit bars in a barcode
	
	public BarcodeAnalyserRobot()
	{
		setupBarcodes();
	}
	public void setWhiteMargin(int whiteMargin){
		this.whiteMargin=whiteMargin;
	}
	public void setBlackMargin(int blackMargin){
		this.blackMargin=blackMargin;
	}
	
	public void SetBrownValue(int value)
	{
		brownValue = value;
	}
	
	/**
	 * Updates the list of readings with given reading and checks if the list contains a valid barcode.
	 * If so, it returns this detected barcode.
	 * @param reading
	 * @return			The detected barcode, or an empty string.
	 */
	public String Update(int reading)
	{
//		System.out.println(reading);
		if(brownValue == 0) 
			brownValue = reading; //making sure brownValue has been set

		readings.add(reading);		
		
//		System.out.println(readings.size());
		
		
		if(getColorFromValue(reading)!=2)
			return "";
		
		String ret = analyse();
		if(!ret.equals(""))
			readings.clear(); //clear the readings list, so you wont keep sending the same bc over and over again
		
		return ret;
	}
	
	
	private String analyse()
	{
		
		//create list with readings of last possible bc reading		
		ArrayList<Integer> data = getLastPossibleReading();
		
		if(data == null)
		{
			return "";
		}
		for(int lastPossible: data){
			System.out.println(lastPossible+" ");
		}
		
		//analyse this list
		String barcode = calculateBarcode(data);		
		String invertedBarcode = invertBarcode(barcode);
		System.out.println(barcode);
		if(isValidBarcode(barcode)||isValidBarcode(invertedBarcode))
			return barcode;
		else
		{
			String recoveredBC = recover(barcode);
			String recoveredInvertedBC = recover(invertedBarcode);
			
			if(isValidBarcode(recoveredBC)||isValidBarcode(recoveredInvertedBC))
				return recoveredBC;
		}
		
		return "";
	}
	
	/**
	 * Returns a list which contains the readings of the last possible read barcode.
	 */
	public ArrayList<Integer> getLastPossibleReading()
	{
		
		lastPossible.clear();
		if(readings.size()<nbBrownValuesMargin){
//			System.out.println("1");
			return null;
			
		}
		for(int i=0; i<nbBrownValuesMargin; i++)
		{
			int k= (readings.size()-i-1);
			if(getColorFromValue(readings.get(k)) != 2){
//				System.out.println("2");
				return null; //you're in the middle of a barcode, no detection possible
			}
		}	
			
		
		//pinch the last brown values from the list (1)
		//add values of the possible barcode to ret (2)
		//stop adding when a lot of brown values are detected again
		//remove last added brown values (3)
		//invert ret (since you started adding from the last reading to the first) (4)
		int index = readings.size()-1;
		
		//(1)
		while(getColorFromValue(readings.get(index)) == 2)
		{
			index--;
			if(index < 0){
				System.out.println("3");
				return null;
			}
		}
		
		int nbBrownValues = 0;		
		
		//(2)
		while(nbBrownValues <= nbBrownValuesMargin)
		{
			int c = readings.get(index);
			if(getColorFromValue(c) == 2)
				nbBrownValues++;
			else
				nbBrownValues = 0;
			
			lastPossible.add(c);
			index--;
			if(index < 0){
				System.out.println("4");
				return null;
			}
		}
		
		//(3)
		for(int i=0; i<=nbBrownValuesMargin; i++)
		{
			lastPossible.remove(lastPossible.size()-1);
		}
		temp.clear();
		
		//(4)
		for(int i=lastPossible.size()-1; i>=0; i--)
		{
			temp.add(lastPossible.get(i));
		}
		
		lastPossible = temp;	
		
		return lastPossible;
	}
		
	/**
	 * Returns a string which contains a barcode derived from given readings. 
	 * It divides the readings in BCLength equal blocks and calculates the mean of each
	 * block.
	 */
	public String calculateBarcode(ArrayList<Integer> data) {

		int barlength = Math.round(data.size() / BCLength);
		colors.clear();

		for (int i = 0; i < BCLength; i++) {

			double tempsum = 0;
			for (int j = 0; j < barlength; j++) {
				tempsum += data.get(i * barlength + j);
			}
			double meanValue = tempsum / barlength;
			
			colors.add(getColorFromValueBin((int) meanValue));
		}

		// prepare string to return
		String ret = "";
		for (int i = 0; i < colors.size(); i++) {
			int color = colors.get(i);
			ret = ret + color;
		}

		return ret;
	}
	
	/**
	 * black = 0; white = 1; brown = 2
	 */
	public int getColorFromValue(int value)
	{
		if (value > brownValue+whiteMargin){
			return 1;
		}
		if(value < brownValue-blackMargin){
			return 0;
		}
		else{
			return 2;
		}
	}
	
	/**
	 * black = 0; white = 1
	 */
	private int getColorFromValueBin(int value) {
		// Detected white
		if (value > brownValue)
			return 1;
		// Detected black
		else
			return 0;
	}
	
	private boolean isValidBarcode(String bc)
	{
		for(int i=0; i<barcodes.size(); i++)
		{
			if(barcodes.get(i).equals(bc))
				return true;
		}
		return false;
	}
	
	/**
	 * Inverts the given string and returns the inversion
	 */
	private String invertBarcode(String detectedBarcode) {
		String ret = "";
		for (int i = detectedBarcode.length() - 1; i >= 0; i--) {
			ret += detectedBarcode.charAt(i);
		}
		return ret;
	}
	
	private String recover(String bc) {
		//System.out.println("Recovery 2");

		String recoveredBc = "";
		int minDist = 100;
		for (int i = 0; i < barcodes.size(); i++) {
			int hammingDist = getHammingDist(bc, barcodes.get(i));

			if (hammingDist < minDist) {
				minDist = hammingDist;
				recoveredBc = barcodes.get(i);
			}
		}
		
		if(minDist > 1)
			return "";
				
		return recoveredBc;
	}

	private int getHammingDist(String s1, String s2) {
		int ret = 0;

		for (int i = 0; i < s1.length(); i++) {
			if (s1.charAt(i) != s2.charAt(i))
				ret++;
		}
		return ret;
	}
		
	private void setupBarcodes()
	{
		barcodes.add("01010110");
		barcodes.add("00100000");
		barcodes.add("01110100");
		barcodes.add("00100000");
		barcodes.add("00101010");
		barcodes.add("00100110");
	
	}
	
	public void TestGetLastPossibleReading()
	{
		//setting up data
		brownValue = (int) (255*0.5f);
		
		addRandomValues(brownValue, 20, 30); 	//adding brown
		addRandomValues(220, 20, 100); 			//adding white
		addRandomValues(20, 20, 100); 			//adding black
		addRandomValues(brownValue, 20, 150); 	//adding brown
		addRandomValues(220, 20, 100); 			//adding white
		addRandomValues(20, 20, 100); 			//adding black
		addRandomValues(brownValue, 20, 30); 	//adding brown
				
		
		String readContent = "";
		for(int i=0; i<readings.size();i++)
		{
			readContent += readings.get(i);
			readContent += " ";
		}
		System.out.println("Readings:");
		System.out.println(readContent);
		
		//actual test
		ArrayList<Integer> testResult = getLastPossibleReading();
		
		
		String output = "";
		for(int i=0; i<testResult.size();i++)
		{
			output += testResult.get(i);
			output += " ";
		}		
		System.out.println("Result:");
		System.out.println(output);
		
	}
	
	private void addRandomValues(int center, int offset, int count)
	{
		for(int i=0; i<count; i++)
		{
			if(Math.random()<0.5)
				readings.add((int)(Math.random()*offset + center));
			else
				readings.add((int)(-Math.random()*offset + center));
		}		
	}
	
	public void TestCalculateBarcode()
	{
		//setting up data: "01011010"
		brownValue = (int) (255*0.5f);
		int offset = 20;
		
		addRandomValues(20, offset, 20); 			//adding black
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(220, offset, 20); 			//adding white
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(20, offset, 20); 			//adding black
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(220, offset, 20); 			//adding white
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(220, offset, 20); 			//adding white
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(20, offset, 20); 			//adding black
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(220, offset, 20); 			//adding white
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(20, offset, 20); 			//adding black
		
		//String result = calculateBarcode(readings);
		
		String data = "";
		for(int i=0; i<readings.size(); i++)
		{
			data += readings.get(i);
			data += " ";
		}
		
		System.out.println("Data-input:");
		System.out.println(data);
		System.out.println("Expected: 01011010");
		//System.out.println("Calculated: " +result);
	}
	
	public void TestUpdate()
	{
		//setting up data: some brown +01011010+ some brown
		barcodes.add("01011010");
		brownValue = (int) (255*0.5f);
		int offset = 20;
		
		addRandomValues(brownValue, offset, 150); 	//brown
		
		addRandomValues(20, offset, 20); 			//adding black
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(220, offset, 20); 			//adding white
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(20, offset, 20); 			//adding black
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(220, offset, 20); 			//adding white
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(220, offset, 20); 			//adding white
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(20, offset, 20); 			//adding black
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(220, offset, 20); 			//adding white
		addRandomValues(brownValue, offset, 10); 	//brown
		addRandomValues(20, offset, 20); 			//adding black
		
		addRandomValues(brownValue, offset, 100); 	//brown
		
		
		ArrayList<Integer> data = new ArrayList<Integer>();
		for(int i=0; i<readings.size(); i++)
		{
			data.add(readings.get(i));
		}
		
		readings.clear();
				
		for(int i=0; i<data.size(); i++)
		{
			String tempResult = Update(data.get(i));
			if(!tempResult.equals(""))
				System.out.println(tempResult);
		}
		
		System.out.println("Expected: 01011010");
	}
	
	
}

